package de.hmm.hwip.security.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

import android.os.Environment;
import android.util.Base64;
import android.util.Log;
import de.hmm.hwip.security.api.ISecurityClient;

/**
 * Class to handle de- and encryption with AES and RSA algorithm.
 * 
 * @author johannes mendel
 * @version 30.10.2012
 */
public class ClientSecurityImpl implements ISecurityClient {

	/** Class identifier to debug **/
	final String TAG = "Securitey Client Impl";
	
	/** AES key used for de- and encryption*/
	private SecretKey key;
	
	/** This vector initializes the AES algorithm */
	private IvParameterSpec initVector;
	
	/** Public key used for de- and encryption */
	private static PublicKey publicKey = null;
	
	/** path to security files */
	private String path ="";
	
	/**
	 * Constructs a new security service implementation.
	 * 
	 */
	public ClientSecurityImpl(){
		try{
			//getting direction
			this.path = Environment.getExternalStorageDirectory().getAbsolutePath();
			this.path += "/hwip/security/";
			
			//check if thread had been started before
			if(publicKey == null)
				publicKey = loadPublicKey();
					    
		}catch(Exception e){
			Log.e(TAG, "Exception: " + e.getLocalizedMessage());
		}
	}
	
	/**
	 * Decrypting the given message with AES algorithm.
	 * 
	 * @param message - String to decrypt
	 * 
	 * @return decrypted string
	 */
	@Override
	public String decryptStringAES(String message) {
		try
		{
	        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
	        
	        cipher.init(Cipher.DECRYPT_MODE, this.key, initVector);
	        byte[] original = cipher.doFinal(Base64.decode(message, Base64.DEFAULT));
	        
	        return new String(original);
		}
		catch(Exception e)
		{
			new CryptoException(e.getLocalizedMessage(), e);
		}
		return null;
	}

	/**
	 * Decrypting the given message
	 * 
	 * @param message - string to decrypt
	 * 
	 * @return decrypted string
	 */
	@Override
	public String decryptStringRSA(String message)
	{
		try 
	    {
			//creating the cipher
			Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
			cipher.init(Cipher.DECRYPT_MODE, publicKey);
			
			byte[] cipherData = Base64.decode(message, Base64.DEFAULT); //decoding base64
			cipherData = cipher.doFinal(cipherData); //decrypt
			
			return new String(cipherData);
		} 
		catch (Exception e) 
		{
			new CryptoException(e.getLocalizedMessage(), e);
		}
		return null;
	}

	/**
	 * Encrypting the given message with AES algorithm.
	 * 
	 * @param message - String to encrypt
	 * 
	 * @return encrypted, base64 encoded string.
	 */
	@Override
	public String encryptStringAES(String message) {
		try 
		{
			//configuring the cipher.
			Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
			cipher.init(Cipher.ENCRYPT_MODE, this.key, initVector);

			return new String(Base64.encode(cipher.doFinal(message.getBytes()), Base64.NO_WRAP));
		} 
		catch (Exception e) 
		{
			new CryptoException(e.getLocalizedMessage(), e);
		} 
		return null;
	}

	/**
	 * Encrypting the given message
	 * 
	 * @param message - string to encrypt
	 * 
	 * @return encrypted, base64 encoded string.
	 */
	@Override
	public String encryptStringRSA(String message){
		try 
	    {
			//creating the cipher
			Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding");
			cipher.init(Cipher.ENCRYPT_MODE, publicKey);
			byte[] cipherData = cipher.doFinal(message.getBytes()); //encrypt
			
			return Base64.encodeToString(cipherData, Base64.DEFAULT);
		} 
	    catch (Exception e) 
	    {
	    	new CryptoException(e.getLocalizedMessage(), e);
		}
		return null;
	}
	
	/**
	 * Loading the RSA public key from public.key file
	 * 
	 * @return public key
	 * @throws CryptoException Is thrown if loading, casting or creating fails.
	 */
	private PublicKey loadPublicKey() throws CryptoException
	{
		try 
		{
			InputStream in = new FileInputStream(new File(path + "public.key")); //creating the input stream.
			ObjectInputStream ois = new ObjectInputStream(in);
			
			//reading the serialized values
			BigInteger modulus = (BigInteger) ois.readObject();
			BigInteger exponent = (BigInteger) ois.readObject();
			
			//creating the public key spec.
			KeyFactory fact = KeyFactory.getInstance("RSA");
		 	RSAPublicKeySpec publicKeySpec = new RSAPublicKeySpec(modulus, exponent);
		
			return fact.generatePublic(publicKeySpec); //generate and return public key.
		} 
		catch (Exception e)
		{
			throw new CryptoException(e.getLocalizedMessage(), e);
		}
	}

	@Override
	public void setAESKey(SecretKey key) {
		this.key = key;
		this.initVector = new IvParameterSpec(this.key.getEncoded());
	}
	
}
